/*
 * Project: Sudoku Hint Engine for Android
 * Copyright (C) 2010-2013 Emanuel Moecklin
 * 
 * Available under the terms of the Lesser General Public License (LGPL)
 */
package com.onegravity.sudoku.solver.checks;

import java.util.BitSet;
import java.util.HashMap;
import java.util.Map;

import com.onegravity.sudoku.solver.core.FinalHintProducer;
import com.onegravity.sudoku.solver.core.WarningHintProducer;
import com.onegravity.sudoku.solver.data.Cell;
import com.onegravity.sudoku.solver.data.Grid;
import com.onegravity.sudoku.solver.util.BitSetFactory;
import com.onegravity.sudoku.solver.util.BitSetIterator;
import com.onegravity.sudoku.solver.util.SudokuValidator;

/**
 * This class checks that all pencil marks are correct and none is missing.
 * @author Emanuel Moecklin
 */
public abstract class PencilMarksHintsProducer implements WarningHintProducer, FinalHintProducer {

	protected Map<Cell, BitSet> mToBeAdded; 
	protected Map<Cell, BitSet> mToBeRemoved;

	private Grid mGrid;
	private Grid mSolvedGrid;
	private SudokuValidator mValidator;

	public PencilMarksHintsProducer(Grid solvedGrid) {
		mSolvedGrid = solvedGrid;
		mToBeAdded = new HashMap<Cell, BitSet>();
		mToBeRemoved = new HashMap<Cell, BitSet>();
	}

	/**
	 * Collect all missing and incorrect candidates in the given grid.
	 * The result of this processing is available through the protected instance variables
	 * @return True if the grid contains no candidates at all, False otherwise
	 */
	protected boolean processGrid(Grid grid) {
		boolean hasNoCandidates = true;
		if (mSolvedGrid!=null && grid!=null) {
			mGrid = grid;
	    	mValidator = new SudokuValidator(grid);
			mToBeAdded.clear();
			mToBeRemoved.clear();
			for (int y=0; y<9; y++) {
				for (int x=0; x<9; x++) {
					hasNoCandidates = hasNoCandidates & processCell(x, y);
				}
			}
		}
		return hasNoCandidates;
	}

	/**
	 * Checks if a cell has missing or incorrect candidates.
	 * Candidates are missing if the cell contains no digit and candidates at all or if the correct one according to the solution is missing.
	 * Candidates are incorrect if there are set digits in the same row, column, block or extra region. 
	 * @return True if the cell contains no candidates at all, False otherwise
	 */
    private boolean processCell(int x, int y) {
		boolean hasNoCandidates = true;

		Cell cell = mGrid.getCell(x, y);
		
		// 1) potentials are correct if cell has been solved already (or was a given)
		if (cell.isEmpty()) {
			BitSet potentials = cell.getPotentialValues();
			
			// 2) compute potentials for cells without any
			if (potentials.cardinality()==0) {
				mToBeAdded.put(cell, (BitSet) mValidator.getCandidates(x, y));
			}
			else {
				hasNoCandidates = false;

				// 3) cells with pencil marks must contain correct digit as potential
				int solutionValue = mSolvedGrid.getCell(x, y).getValue();
				if (! cell.hasPotentialValue(solutionValue)) {
					mToBeAdded.put(cell, BitSetFactory.createOneElement(solutionValue));
				}

				// 4) remove potentials that are illogical (digit already in block, row, column or extra region) 
				BitSet potentials2beRemoved = null;
				for (BitSetIterator<Integer> it=new BitSetIterator<Integer>(potentials); it.hasNext(); ) {
					int value = it.next();
					if (! mValidator.checkValue(x, y, value) ) {
						if (potentials2beRemoved==null) {
							potentials2beRemoved = new BitSet();
							mToBeRemoved.put(cell, potentials2beRemoved);
						}
						potentials2beRemoved.set(value);
					}
				}
			}
		}
		
		return hasNoCandidates;
	}

    @Override
	public String getProducerName() {
		return null;
	}
}